//! Declaration AST nodes for NRC language

use crate::ast::expr::Expression;
use crate::ast::types::Type;
use crate::error::Location;
use serde::{Deserialize, Serialize};

/// All possible declarations in NRC
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Declaration {
    /// Function declaration
    Function(FunctionDecl),
    /// Variable declaration
    Variable(VariableDecl),
    /// Constant declaration
    Constant(ConstantDecl),
    /// Type declaration
    Type(TypeDecl),
    /// Struct declaration
    Struct(StructDecl),
    /// Enum declaration
    Enum(EnumDecl),
    /// Trait declaration
    Trait(TraitDecl),
    /// Trait implementation
    TraitImpl(TraitImpl),
    /// Import declaration
    Import(ImportDecl),
    /// Implementation declaration
    Impl(ImplDecl),
}

/// Function declaration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionDecl {
    /// Function name
    pub name: String,
    /// Generic type parameters
    pub generics: Vec<GenericParam>,
    /// Function parameters
    pub parameters: Vec<crate::ast::types::Parameter>,
    /// Return type
    pub return_type: Option<Type>,
    /// Function body
    pub body: Option<crate::ast::Block>,
    /// Function attributes
    pub attributes: Vec<Attribute>,
    /// Location in source
    pub location: Location,
}

/// Generic type parameter
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenericParam {
    /// Parameter name
    pub name: String,
    /// Type constraints
    pub constraints: Vec<Type>,
    /// Location in source
    pub location: Location,
}

/// Variable declaration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VariableDecl {
    /// Variable name
    pub name: String,
    /// Variable type (if specified)
    pub var_type: Option<Type>,
    /// Initial value
    pub initializer: Option<Expression>,
    /// Is mutable
    pub mutable: bool,
    /// Location in source
    pub location: Location,
}

/// Constant declaration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConstantDecl {
    /// Constant name
    pub name: String,
    /// Constant type (if specified)
    pub const_type: Option<Type>,
    /// Constant value
    pub value: Expression,
    /// Location in source
    pub location: Location,
}

/// Type declaration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypeDecl {
    /// Type name
    pub name: String,
    /// Type definition
    pub type_def: Type,
    /// Location in source
    pub location: Location,
}

// Parameter struct is defined in types.rs to avoid duplication

/// Struct declaration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StructDecl {
    /// Struct name
    pub name: String,
    /// Generic type parameters
    pub generics: Vec<GenericParam>,
    /// Struct fields
    pub fields: Vec<crate::ast::StructField>,
    /// Struct methods
    pub methods: Vec<FunctionDecl>,
    /// Location in source
    pub location: Location,
}

/// Enum declaration (Rust-style: type Name enum { ... })
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnumDecl {
    /// Enum name
    pub name: String,
    /// Generic type parameters
    pub generics: Vec<GenericParam>,
    /// Enum variants
    pub variants: Vec<EnumVariant>,
    /// Location in source
    pub location: Location,
}

/// Enum variant
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnumVariant {
    /// Variant name
    pub name: String,
    /// Variant data type (if any)
    /// None for unit variants (e.g., Pending, Running)
    /// Some(Type) for data variants (e.g., Failed(string))
    pub data: Option<EnumVariantData>,
    /// Location in source
    pub location: Location,
}

/// Enum variant data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EnumVariantData {
    /// Tuple-like variant: Some(T), Failed(string, int32)
    Tuple(Vec<Type>),
    /// Struct-like variant: Point { x: int32, y: int32 }
    Struct(Vec<crate::ast::StructField>),
}

/// Trait declaration (type Name trait { ... })
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraitDecl {
    /// Trait name
    pub name: String,
    /// Generic type parameters
    pub generics: Vec<GenericParam>,
    /// Trait methods (signatures only, no implementation)
    pub methods: Vec<TraitMethod>,
    /// Location in source
    pub location: Location,
}

/// Trait method signature
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraitMethod {
    /// Method name
    pub name: String,
    /// Method parameters
    pub parameters: Vec<crate::ast::types::Parameter>,
    /// Return type
    pub return_type: Option<Type>,
    /// Location in source
    pub location: Location,
}

/// Trait implementation (impl Trait for Type { ... })
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraitImpl {
    /// Trait name
    pub trait_name: String,
    /// Type name being implemented
    pub for_type: String,
    /// Generic type parameters
    pub generics: Vec<GenericParam>,
    /// Methods implementing the trait
    pub methods: Vec<FunctionDecl>,
    /// Location in source
    pub location: Location,
}

/// Import declaration (ES6-style)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportDecl {
    /// Import path (string literal: "./foo.nr", "@project/bar.nr", etc.)
    pub path: String,
    /// Default import (import foo from "...")
    pub default_import: Option<String>,
    /// Named imports with optional aliases (import { a, b as c } from "...")
    pub named_imports: Vec<ImportItem>,
    /// Namespace import (import * as foo from "...")
    pub namespace_import: Option<String>,
    /// Location in source
    pub location: Location,
}

/// A single imported item with optional alias
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportItem {
    /// Original name in the module
    pub name: String,
    /// Alias (if renamed with 'as')
    pub alias: Option<String>,
}

/// Implementation declaration (Rust-style: impl Type { ... })
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImplDecl {
    /// Type name being implemented
    pub type_name: String,
    /// Generic type parameters
    pub generics: Vec<GenericParam>,
    /// Methods in this implementation
    pub methods: Vec<FunctionDecl>,
    /// Location in source
    pub location: Location,
}

/// Function or method attribute
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attribute {
    /// Attribute name
    pub name: String,
    /// Attribute arguments
    pub arguments: Vec<Expression>,
    /// Location in source
    pub location: Location,
}

impl Declaration {
    /// Get the location of this declaration
    pub fn location(&self) -> Location {
        match self {
            Declaration::Function(decl) => decl.location,
            Declaration::Variable(decl) => decl.location,
            Declaration::Constant(decl) => decl.location,
            Declaration::Type(decl) => decl.location,
            Declaration::Struct(decl) => decl.location,
            Declaration::Enum(decl) => decl.location,
            Declaration::Trait(decl) => decl.location,
            Declaration::TraitImpl(decl) => decl.location,
            Declaration::Import(decl) => decl.location,
            Declaration::Impl(decl) => decl.location,
        }
    }

    /// Get the name of this declaration
    pub fn name(&self) -> &str {
        match self {
            Declaration::Function(decl) => &decl.name,
            Declaration::Variable(decl) => &decl.name,
            Declaration::Constant(decl) => &decl.name,
            Declaration::Type(decl) => &decl.name,
            Declaration::Struct(decl) => &decl.name,
            Declaration::Enum(decl) => &decl.name,
            Declaration::Trait(decl) => &decl.name,
            Declaration::TraitImpl(decl) => &decl.trait_name,
            Declaration::Import(decl) => &decl.path,
            Declaration::Impl(decl) => &decl.type_name,
        }
    }
}

impl FunctionDecl {
    /// Check if this function is a method
    pub fn is_method(&self) -> bool {
        self.attributes.iter().any(|attr| attr.name == "method")
    }

    /// Check if this function is a constructor
    pub fn is_constructor(&self) -> bool {
        self.attributes
            .iter()
            .any(|attr| attr.name == "constructor")
    }

    /// Check if this function is a destructor
    pub fn is_destructor(&self) -> bool {
        self.attributes.iter().any(|attr| attr.name == "destructor")
    }

    /// Get the receiver type if this is a method
    pub fn receiver_type(&self) -> Option<&Type> {
        if self.is_method() && !self.parameters.is_empty() {
            Some(&self.parameters[0].param_type)
        } else {
            None
        }
    }
}

impl StructDecl {
    /// Get a field by name
    pub fn get_field(&self, name: &str) -> Option<&crate::ast::StructField> {
        self.fields.iter().find(|field| field.name == name)
    }

    /// Get a method by name
    pub fn get_method(&self, name: &str) -> Option<&FunctionDecl> {
        self.methods.iter().find(|method| method.name == name)
    }

    /// Check if this struct has a field with the given name
    pub fn has_field(&self, name: &str) -> bool {
        self.fields.iter().any(|field| field.name == name)
    }

    /// Check if this struct has a method with the given name
    pub fn has_method(&self, name: &str) -> bool {
        self.methods.iter().any(|method| method.name == name)
    }
}

impl EnumDecl {
    /// Get a variant by name
    pub fn get_variant(&self, name: &str) -> Option<&EnumVariant> {
        self.variants.iter().find(|variant| variant.name == name)
    }

    /// Check if this enum has a variant with the given name
    pub fn has_variant(&self, name: &str) -> bool {
        self.variants.iter().any(|variant| variant.name == name)
    }
}

impl TraitDecl {
    /// Get a method by name
    pub fn get_method(&self, name: &str) -> Option<&TraitMethod> {
        self.methods.iter().find(|method| method.name == name)
    }

    /// Check if this trait has a method with the given name
    pub fn has_method(&self, name: &str) -> bool {
        self.methods.iter().any(|method| method.name == name)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast::types::Parameter;
    use crate::ast::StructField;

    #[test]
    fn test_function_declaration() {
        let location = Location::new(1, 1, 0);
        let func_decl = FunctionDecl {
            name: "add".to_string(),
            generics: vec![],
            parameters: vec![
                Parameter {
                    name: "a".to_string(),
                    param_type: Type::Basic(crate::ast::types::BasicType::Int),
                    default_value: None,
                    location,
                },
                Parameter {
                    name: "b".to_string(),
                    param_type: Type::Basic(crate::ast::types::BasicType::Int),
                    default_value: None,
                    location,
                },
            ],
            return_type: Some(Type::Basic(crate::ast::types::BasicType::Int)),
            body: None,
            attributes: vec![],
            location,
        };

        assert_eq!(func_decl.name, "add");
        assert_eq!(func_decl.parameters.len(), 2);
        assert!(!func_decl.is_method());
    }

    #[test]
    fn test_struct_declaration() {
        let location = Location::new(1, 1, 0);
        let field = StructField {
            name: "x".to_string(),
            field_type: Type::Basic(crate::ast::types::BasicType::Int),
            default_value: None,
            field_tag: None,
            location,
        };

        let struct_decl = StructDecl {
            name: "Point".to_string(),
            generics: vec![],
            fields: vec![field],
            methods: vec![],
            location,
        };

        assert_eq!(struct_decl.name, "Point");
        assert_eq!(struct_decl.fields.len(), 1);
        assert!(struct_decl.has_field("x"));
        assert!(!struct_decl.has_field("y"));
    }

    #[test]
    fn test_declaration_location() {
        let location = Location::new(5, 10, 100);
        let decl = Declaration::Function(FunctionDecl {
            name: "test".to_string(),
            generics: vec![],
            parameters: vec![],
            return_type: None,
            body: None,
            attributes: vec![],
            location,
        });

        assert_eq!(decl.location(), location);
        assert_eq!(decl.name(), "test");
    }
}
